package beautiful.butterfly.drds.data_exchange.statistics;


import beautiful.butterfly.drds.data_exchange.statistics.jvm_info.*;
import lombok.extern.slf4j.Slf4j;

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.reflect.Method;
import java.util.List;

@Slf4j
public class JvmInfo
{
    public static final long mb = 1024 * 1024;
    public static final long gb = 1024 * 1024 * 1024;
    //
    public static Object lock = new Object();
    private static JvmInfo jvmInfo;


    //
    public final MemoryPoolMXBeanNameToMemoryStatusMap memoryPoolMXBeanNameToMemoryStatusMap = new MemoryPoolMXBeanNameToMemoryStatusMap();
    public final GarbageCollectorMXBeanNameToGarbageCollectorStatusMap garbageCollectorMXBeanNameToGarbageCollectorStatusMap = new GarbageCollectorMXBeanNameToGarbageCollectorStatusMap();
    public final OperatingSystemStatus operatingSystemStatus;
    //上面都是init的时候进行初始化
    public final CpuStatus cpuStatus = new CpuStatus(this);
    private final OperatingSystemMXBean operatingSystemMXBean;
    private final String operatingSystemMXBeanInfo;
    //
    private final int availableProcessors;
    private final List<MemoryPoolMXBean> memoryPoolMXBeanList;
    private final List<GarbageCollectorMXBean> garbageCollectorMXBeanList;
    //
    private final RuntimeMXBean runtimeMXBean;
    private final String runtimeMXBeanInfo;
    //ms
    private long lastUpTime = 0;
    //nano
    private long lastProcessCpuTime = 0;

    private JvmInfo()
    {

        this.operatingSystemMXBean = java.lang.management.ManagementFactory.getOperatingSystemMXBean();
        this.operatingSystemMXBeanInfo = this.operatingSystemMXBean.getName() + " " + this.operatingSystemMXBean.getArch() + " " + this.operatingSystemMXBean.getVersion();
        this.availableProcessors = this.operatingSystemMXBean.getAvailableProcessors();
        //
        this.memoryPoolMXBeanList = java.lang.management.ManagementFactory.getMemoryPoolMXBeans();
        if (this.memoryPoolMXBeanList != null && !this.memoryPoolMXBeanList.isEmpty())
        {
            for (MemoryPoolMXBean memoryPoolMXBean : this.memoryPoolMXBeanList)
            {
                MemoryStatus memoryStatus = new MemoryStatus();
                memoryStatus.name = memoryPoolMXBean.getName();
                memoryStatus.initSize = memoryPoolMXBean.getUsage().getInit();
                memoryStatus.maxSize = memoryPoolMXBean.getUsage().getMax();
                this.memoryPoolMXBeanNameToMemoryStatusMap.memoryPoolMXBeanNameToMemoryStatusMap.put(memoryPoolMXBean.getName(), memoryStatus);
            }
        }
        //
        this.garbageCollectorMXBeanList = java.lang.management.ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean garbageCollectorMXBean : this.garbageCollectorMXBeanList)
        {
            GarbageCollectorStatus garbageCollectorStatus = new GarbageCollectorStatus();
            garbageCollectorStatus.name = garbageCollectorMXBean.getName();
            this.garbageCollectorMXBeanNameToGarbageCollectorStatusMap.garbageCollectorMXBeanNameToGarbageCollectorStatusMap.put(garbageCollectorMXBean.getName(), garbageCollectorStatus);
        }
        //
        this.runtimeMXBean = java.lang.management.ManagementFactory.getRuntimeMXBean();
        this.runtimeMXBeanInfo = this.runtimeMXBean.getVmVendor() + " " + this.runtimeMXBean.getSpecVersion() + " " + this.runtimeMXBean.getVmVersion();
        //
        this.operatingSystemStatus = new OperatingSystemStatus();
        log.info("jvmInfo#operatingSystemMXBean class name:" + this.operatingSystemMXBean.getClass().getName());
        if (jvmInfo.isSunUnixOperatingSystemMXBean(this.operatingSystemMXBean))
        {
            {
                this.operatingSystemStatus.totalPhysicalMemorySize = jvmInfo.invoke(this.operatingSystemMXBean, "getTotalPhysicalMemorySize");
                this.operatingSystemStatus.freePhysicalMemorySize = jvmInfo.invoke(this.operatingSystemMXBean, "getFreePhysicalMemorySize");
                this.operatingSystemStatus.maxFileDescriptorCount = jvmInfo.invoke(this.operatingSystemMXBean, "getMaxFileDescriptorCount");
                this.operatingSystemStatus.openFileDescriptorCount = jvmInfo.invoke(this.operatingSystemMXBean, "getOpenFileDescriptorCount");
            }
        }


    }

    /**
     * @return null or jvmInfo. null is something error, job no care it.
     */
    public static JvmInfo getJvmInfo()
    {
        if (jvmInfo == null)
        {
            synchronized (lock)
            {
                if (jvmInfo == null)
                {
                    try
                    {
                        jvmInfo = new JvmInfo();
                    } catch (Exception e)
                    {
                        e.printStackTrace();
                        log.warn("no need care, the fail is ignored : jvmInfo init failed " + e.getMessage(), e);
                    }
                }
            }

        }
        return jvmInfo;
    }

    public static boolean isSunUnixOperatingSystemMXBean(OperatingSystemMXBean operatingSystemMXBean)
    {
        final String className = operatingSystemMXBean.getClass().getName();

        return "com.sun.management.UnixOperatingSystem".equals(className);
    }

    public static long invoke(OperatingSystemMXBean operatingSystemMXBean, String methodName)
    {
        try
        {
            final Method method = operatingSystemMXBean.getClass().getMethod(methodName, (Class<?>[]) null);
            method.setAccessible(true);
            return (Long) method.invoke(operatingSystemMXBean, (Object[]) null);
        } catch (final Exception e)
        {
            log.info(String.format("OperatingSystemMXBean %s failed, Exception = %s ", methodName, e.getMessage()));
        }

        return -1;
    }

    public String toString()
    {
        return "the machine info  => \n\n"
                + "\truntimeMXBeanInfo:\t" + runtimeMXBeanInfo + "\n"
                + "\toperatingSystemMXBeanInfo:\t" + operatingSystemMXBeanInfo + "\n"
                + "\tcpu num:\t" + availableProcessors + "\n\n"
                + operatingSystemStatus.toString() + "\n"
                + garbageCollectorMXBeanNameToGarbageCollectorStatusMap.toString() + "\n"
                + memoryPoolMXBeanNameToMemoryStatusMap.toString() + "\n";
    }

    public String totalString()
    {
        return (cpuStatus.getTotalString() + garbageCollectorMXBeanNameToGarbageCollectorStatusMap.getTotalString());
    }

    /**
     * 外面调用的方法
     *
     * @param log 是否写入日志
     */
    public synchronized void updateJvmInfo(boolean log)
    {

        try
        {
            if (jvmInfo.isSunUnixOperatingSystemMXBean(operatingSystemMXBean))
            {
                long uptime = runtimeMXBean.getUptime();
                long processCpuTime = invoke(operatingSystemMXBean, "getProcessCpuTime");
                //百分比， uptime是ms，processTime是nano
                if ((uptime > lastUpTime) && (processCpuTime >= lastProcessCpuTime))
                {
                    float cpuUsage = (float) (processCpuTime - lastProcessCpuTime) / ((uptime - lastUpTime) * availableProcessors * 10000);
                    cpuStatus.setCpuUsage(cpuUsage);
                    cpuStatus.averageCpuUsage = (float) processCpuTime / (uptime * availableProcessors * 10000);

                    lastUpTime = uptime;
                    lastProcessCpuTime = processCpuTime;
                }
            }
            if (memoryPoolMXBeanList != null && !memoryPoolMXBeanList.isEmpty())
            {
                for (MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeanList)
                {

                    MemoryStatus memoryStatus = memoryPoolMXBeanNameToMemoryStatusMap.memoryPoolMXBeanNameToMemoryStatusMap.get(memoryPoolMXBean.getName());
                    if (memoryStatus == null)
                    {
                        memoryStatus = new MemoryStatus();
                        memoryStatus.name = memoryPoolMXBean.getName();
                        memoryPoolMXBeanNameToMemoryStatusMap.memoryPoolMXBeanNameToMemoryStatusMap.put(memoryPoolMXBean.getName(), memoryStatus);
                    }
                    memoryStatus.commitedSize = memoryPoolMXBean.getUsage().getCommitted();
                    memoryStatus.setUsedSize(memoryPoolMXBean.getUsage().getUsed());
                    long maxMemorySize = memoryStatus.commitedSize > 0 ? memoryStatus.commitedSize : memoryStatus.maxSize;
                    memoryStatus.setUsedPercent(maxMemorySize > 0 ? (float) 100 * memoryStatus.usedSize / maxMemorySize : -1);
                }
            }
            for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMXBeanList)
            {

                GarbageCollectorStatus garbageCollectorStatus = garbageCollectorMXBeanNameToGarbageCollectorStatusMap.garbageCollectorMXBeanNameToGarbageCollectorStatusMap.get(garbageCollectorMXBean.getName());
                if (garbageCollectorStatus == null)
                {
                    garbageCollectorStatus = new GarbageCollectorStatus();
                    garbageCollectorStatus.name = garbageCollectorMXBean.getName();
                    garbageCollectorMXBeanNameToGarbageCollectorStatusMap.garbageCollectorMXBeanNameToGarbageCollectorStatusMap.put(garbageCollectorMXBean.getName(), garbageCollectorStatus);
                }

                long collectionCount = garbageCollectorMXBean.getCollectionCount();
                garbageCollectorStatus.setCollectionCount(collectionCount);

                long collectionTime = garbageCollectorMXBean.getCollectionTime();
                garbageCollectorStatus.setcollectionTime(collectionTime);
            }


            if (log)
            {
                JvmInfo.log.info(cpuStatus.getDeltaString() + memoryPoolMXBeanNameToMemoryStatusMap.getDeltaString() + garbageCollectorMXBeanNameToGarbageCollectorStatusMap.getDeltaString());
            }

        } catch (Exception e)
        {
            JvmInfo.log.warn("no need care, the fail is ignored : jvmInfo updateJvmInfo failed " + e.getMessage(), e);
        }
    }

}
